# --------------------------------------------------------------------------------------------------------------------
# Project:             Digital Payment System (DPS)
# Website:             http://code.google.com/p/dps-x509/
# Purpose of document: This is the main entry point into the registar web site
# --------------------------------------------------------------------------------------------------------------------
from google.appengine.api import users
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp import util as wutil
import google.appengine.runtime
from time import strftime

import urllib
import sys
import string

#load our vendor packages
sys.path.insert(0, "simplejson")
sys.path.insert(1, "pyasn1")
sys.path.insert(2, "keyczar")
sys.path.insert(3, "dps")

try:
  import simplejson as json
except ImportError:
  import json
  
try:
  import keyczar.keys as keys
except ImportError:
  from keyczar import *
      
import dps.csrAsn1Creator
import dps.csrAsn1Extractor
import dps.csrParams
import dps.errors
import dps.utils
import dps.x509Asn1Creator
import dps.x509AttributeAndValue
import dps.x509Asn1Extractor
import dps.x509CertificateParams
import dps.x509Helper

import model
import roulette
import dealer

# --------------------------------------------------------------------------------------------------------------------
def callWebService(address, params):
  """ call the web service and compare expected and actual results
      @param address - name of the web service to call
      @param params  - struct of input parameters
      @return success/failure and actual result tuple """
  isOk = False
  message = ""
  actual = None
  try:
    global HOST
    encodedParams = urllib.urlencode(params)
    f = urllib.urlopen(address, encodedParams)
    actual = urllib.unquote(f.read())
    f.close()
    isOk = True
  except:
    pass
  dps.utils.out("Calling web service: %s with params: %s result: %s" % (address, params, actual))
  return isOk, actual

# --------------------------------------------------------------------------------------------------------------------
def uploadCerificateToRegistrar(certificate):
  """ send certificate to the registrar. 
      @return success, message tuple. success = true if expected result = actual result"""
  expResult = json.dumps({"returnCode": "True"})  
  isOk, actual = callWebService("%sregistrar/upload" % registrarAddress(), {"certificate": certificate, "format": "json"})
  msg = ""
  if isOk and actual == expResult:
    msg = actual["returnCode"]
  return isOk == expResult, msg

# --------------------------------------------------------------------------------------------------------------------
def findCerificateInRegistrar(certificate):
  """ looks for certificate in registrar. 
      @return true if it is, false otherwise """
  expResult = json.dumps({"returnCode": "True"})
  isOk, msg = callWebService("%sregistrar/exactFind" % registrarAddress(), {"certificate": certificate, "format": "json"})
  isMatch = False
  if isOk:
    isMatch = expResult == msg
  return isMatch

# --------------------------------------------------------------------------------------------------------------------
def currentUsersBalance():
  """ money currently in users account """
  user = users.get_current_user()
  if not user:
    return 0
  
  userentry = None
  dbdata = model.UserData().all()
  for u in dbdata:
    if user == u.user_:
      userentry = u
      break
  
  if not userentry:
    return 0
  
  return userentry.balance_
  
# --------------------------------------------------------------------------------------------------------------------
def registrarAddress():
  """ address to send certificates to """
  return "http://dps-x509.appspot.com/"
  #return "http://localhost:8083/"
  
# --------------------------------------------------------------------------------------------------------------------
def initUserAccount(handler):
  """ create the user csr and x509 self signed cert if it has not already been done """
  user = users.get_current_user()
  
  if not user:
    handler.redirect("/intro/")
    return

  user = users.get_current_user()  
  
  #Check if user already exists current user
  query = model.UserData.all()
  for q in query:
    if q.user_ == user:
      return

  msg = ""
  try:
    privateKey = dps.x509Asn1Creator.generatePrivateKey()
    #to speed things up for testing use this... privateKey = dealer.Dealer.privateKey()
    
    #create csr
    csrParams = dps.csrParams.CsrParams()
    subject = dps.x509AttributeAndValue.X509AttributeAndValueList()
    subjectItem = dps.x509AttributeAndValue.X509AttributeAndValue(user.email())
    subject.addItem(subjectItem)
    csrParams.setSubject(subject)
    csr = dps.csrAsn1Creator.CsrAsn1Creator(csrParams, privateKey)
    
    #create x509
    x509Params = dps.x509CertificateParams.X509CertificateParams()
    x509Params.setSubject(subject)
    x509Params.setIssuer(subject)
    x509 = dps.x509Asn1Creator.X509Asn1Creator(x509Params, privateKey)
    x509.signWithPrivateKey(privateKey)
    
    if not findCerificateInRegistrar(dealer.Dealer.x509CertBase64()):
      isOk, msg = uploadCerificateToRegistrar(dealer.Dealer.x509CertBase64())  
    
    isOk, msg = uploadCerificateToRegistrar(x509.getBase64())
    
    userdata = model.UserData()
    userdata.user_ = user
    userdata.balance_ = 0
    userdata.privateKey_ = str(privateKey)
    userdata.userCsrBase64_ = csr.getBase64()
    userdata.userSelfSignedX509Base64_ = x509.getBase64()
    userdata.put()
  
  except google.appengine.runtime.DeadlineExceededError:
    msg = "Sorry. Operator timed out. Please try again."    
 
  
# --------------------------------------------------------------------------------------------------------------------
def standardHeader():
  """ at top of every html page """
  user = users.get_current_user()
  userStr = "[user unknown]"
  if user:
    userStr = user.nickname()
  
  balance = currentUsersBalance()
  return """<h1>Roulette</h1>User: <b>%s</b> Balance: <b>%d</b><hr/>""" % (userStr, balance)

# --------------------------------------------------------------------------------------------------------------------
def standardFooter():
  """ at bottom of every html page """
  return """<hr/>
            <table>
              <tr>
                <td>&middot</td><td><a href='/roulette/'>Home</a></td>
                <td>&middot</td><td><a href='/roulette/play'>Play</a></td>
                <td>&middot</td><td><a href='/roulette/bank'>Bank</a></td>
				<td>&middot</td><td><a href='http://code.google.com/p/dps-x509/wiki/RouletteSampleApplication'>About</a></td>
                <td>&middot</td><td><a href='https://appengine.google.com/dashboard?app_id=dps-roulette'>Admin</a></td>
				<td>&middot</td><td><a href=%s>Sign Out</a><td><td>&middot</td>
              </tr>
            <table>"""% (users.create_logout_url("/"))

# --------------------------------------------------------------------------------------------------------------------
def bettingHistory():
  """ display a list of previous bets """
  dbdata = model.RouletteHistory().all()
  values = []
  #get the first (and only) one. yep there is def a quicker way, i just 
  for v in dbdata:
    values = v.values_
    break  
  
  pastValuesStr = ""
  for v in values[:10]:
    if pastValuesStr == "":
      pastValuesStr = "<b>Past Spins:</b> %s<br/>" % pastValuesStr
    pastValuesStr += str(v)+"&nbsp;" * 3
      
  return pastValuesStr


# --------------------------------------------------------------------------------------------------------------------
class DpsHandler(webapp.RequestHandler):
  """ Default impl for handlers """
  
  def get(self):
    initUserAccount(self)
    self.redirect("/roulette/")      
    
  def post(self):
    self.error(404)


# --------------------------------------------------------------------------------------------------------------------
class MainHandler(DpsHandler):
  
	def get(self):
		user = users.get_current_user()
	
		if user:
			self.redirect("/roulette/")
		else:
			self.redirect("/intro/")
# --------------------------------------------------------------------------------------------------------------------
class IntroHandler(DpsHandler):
  
  def get(self):
	user = users.get_current_user()
	
	if user:
		self.redirect("/roulette/")
	else:
		self.response.out.write("""<div align=center><h1>Value Transfer Systems <i>Without Third Party Settlement</i></h1>
								<h2>Roulette Sample Application</h2>
								<h2><a href=%s>Login</a></h2></div>
								This sample roulette application demonstrates how an a decentralised currency model could be used in practice. You can view documentation <a href="http://code.google.com/p/dps-x509/wiki/RouletteSampleApplication">about</a> the Roulette Sample Application prototype.
								<br>To try out the "Roulette Sample Application" you are required to <a href=%s>login</a> using your Gmail or Google Account.
								<h2>Concept</h2>The project uses X.509 PKI certificates, as used widely in the internet domain to certify entities. as tokens to represent value as described in the project page."""
								% (users.create_login_url(self.request.uri), users.create_login_url(self.request.uri)))                  
# --------------------------------------------------------------------------------------------------------------------
class RouletteHandler(DpsHandler):
  
  def get(self):
    initUserAccount(self)
    
    self.response.out.write(standardHeader()+"""<h2>What do you want to do?</h2>
                               <ul>
                                  <li><a href='/roulette/play'>Play</a></li>
                                  <li><a href='/roulette/bank'>The bank</a></li>
								  <li>View documentation <a href='http://code.google.com/p/dps-x509/wiki/RouletteSampleApplication'>about</a> this Roulette Sample Application prototype</li>
                                  <li>Go to the Google App Engine <a href='https://appengine.google.com/dashboard?app_id=dps-roulette'>Admin</a> Console</li>
								  <li><a href=%s>Sign Out</a></li>
							   </ul>"""% (users.create_logout_url("/")))

    
# --------------------------------------------------------------------------------------------------------------------
class PlayHandler(DpsHandler):
  """ play roulette """

  def get(self):
    initUserAccount(self)
  
    self.response.out.write("""%s<form method='post' action='/roulette/play'>
                               %s<br/>
                               <input type='submit' value='Bet!'/>
                               </form>
                               %s%s""" % \
                            (standardHeader(), roulette.RouletteBoard().getHtmlTable(), bettingHistory(), standardFooter()))  
  def post(self):
    user = users.get_current_user()      

    userentry = None
    dbdata = model.UserData().all()
    for u in dbdata:
      if user == u.user_:
        userentry = u
        break

    dps.utils.assertion(userentry, "User must not be null")

    rb = roulette.RouletteBoard()
    vmap = rb.getUserInputs(self.request)
    bet = roulette.calculateBet(vmap)
    
    msg = ""
    if userentry.balance_ < bet:
      msg = "You have insufficient funds. To get more go to the <a href='/roulette/bank'>bank</a>."
    if bet == 0:
      msg = "Please place a bet."
    
    if not msg == "":
      self.response.out.write("""%s<br/>%s
                               <form method='post' action='/roulette/play'>
                               %s<br/>
                               <input type='submit' value='Bet Again!'/>
                               </form>
                               %s%s""" % \
                               (standardHeader(), msg, rb.getHtmlTable(vmap), bettingHistory(), standardFooter()))
    else:        
      spin = roulette.getSpin()
      
      values = []
      dbdata = model.RouletteHistory().all()
      vdb = None
      #get the first (and only) one
      for v in dbdata:
        vdb = v
        values = v.values_
        break
      
      values = [spin] + values[:9] #only keep last 10
      if not vdb:
        vdb = model.RouletteHistory()
      vdb.values_ = values
      vdb.put()
            
      payout = rb.calculatePayout(spin, vmap)
      
      userentry.balance_ += payout - bet
      userentry.put()
          
      self.response.out.write("""%s
                                <form method='post' action='/roulette/play'>
                                %s<br/>
                                <input type='submit' value='Bet Again!'/>
                                </form>
                                <table>
                                  <tr><td>Spin:</td><td>%d</td></tr>
                                  <tr><td>Bet:</td><td>%d</td></tr>
                                  <tr><td>Win:</td><td>%d</td></tr>
                                </table>
                                %s%s""" % (standardHeader(), rb.getHtmlTable(vmap, spin), spin, bet, payout, bettingHistory(), standardFooter()))
      
# --------------------------------------------------------------------------------------------------------------------
class BankHandler(DpsHandler):
  """ move money in and out of the bank """
  
  # --------------------------------------------------------------------------------------------------------------------
  def __bankPage(self, messageText=""):
    """ text to display for bank UI 
    @param message text can optionally display a warning/error message to user."""
    user = users.get_current_user()     
    dps.utils.assertion(user, "User must not be null") 
    
    userentry = None
    dbdata = model.UserData().all()
    for u in dbdata:
      if user == u.user_:
        userentry = u
        break

    dps.utils.assertion(userentry, "User must not be null") 
      
    dbdata = model.Transaction().all()
    
    balance = currentUsersBalance()
    winnings = balance
    balanceTable = ""
    for t in dbdata:
      if t.user_ and t.user_.user_ == userentry.user_:
        winnings -= t.tokenValue_
        tt = "Top up"
        if t.tokenValue_ < 0:
          tt = "Cash out"
        balanceTable += """<tr><td>%s</td><td align='right'>%d</td><td>%s</td><td>
                          <a href='/roulette/detail?id=%s'>detail</a></td></tr>""" % \
                     (t.date_, t.tokenValue_, tt, t.key())
    
    balanceTable = """<table border='1'>
    <tr><th>Date</th><th>Value</th><th>Type</th><th>Detail</t></tr>
    %s
    <tr><td>&nbsp</td><td align='right'>%d</td><td>Winnings</td><td>n/a</td></tr>
    <tr><td>&nbsp</td><td align='right'>%d</td><td>Balance</td><td>&nbsp;</td></tr>
    </table>""" % (balanceTable, winnings, balance)
    
    return """%s<h2>The Bank</h2>
    <table>
    <tr><td>%s</td></tr>
    <tr><form method='post' action='/roulette/bank'><td>Top up?</td>
            <td><input type='int' name='value'/></td>
            <td><input type='submit' value='Enter'/></td>
            <td><input type='hidden' name='action' value='add'/></td></form></tr>
    <tr><form method='post' action='/roulette/bank'	><td>Cash out?</td>
            <td><input type='int' name='value'/></td>
            <td><input type='submit' value='Enter'/></td>
            <td><input type='hidden' name='action' value='remove'/></td></form></tr>
            <tr><td></td></tr></table>
            <h3>Transactions</h3>
    %s%s""" % (standardHeader(), messageText, balanceTable, standardFooter())
  
  def get(self):
    initUserAccount(self)
    
    self.response.out.write(self.__bankPage())

  def post(self):
    userentry = None
    user = users.get_current_user()      

    dbdata = model.UserData().all()
    for u in dbdata:
      if user == u.user_:
        userentry = u
        break

    dps.utils.assertion(userentry, "User must not be null")
      
    action = self.request.get("action")
    
    value = dps.utils.toInt(self.request.get("value"))
    if value <= 0:
      self.response.out.write(self.__bankPage("Integer value required"))
      return
    
    issuerCert = ""
    issuerPrivateKeyStr = ""
    recieverCsrStr = ""
    receiverPrivateKeyStr = ""
    
    if action == "remove":
      #create a cert from the dealer
      if value > userentry.balance_:
        self.response.out.write(self.__bankPage("Insufficient funds for transfer"))
        return
      
      value = -value
        
      issuerPrivateKeyStr = dealer.Dealer.privateKeyStr()
      issuerCert = dealer.Dealer.x509CertBase64()
      receiverPrivateKeyStr = userentry.privateKey_ #usually this would be done client side...
      receiverCsrStr = userentry.userCsrBase64_
      
    if action == "add":
      # create a certificate for the dealer
      issuerPrivateKeyStr = userentry.privateKey_
      issuerCert = userentry.userSelfSignedX509Base64_
      receiverPrivateKeyStr = dealer.Dealer.privateKeyStr()
      receiverCsrStr = dealer.Dealer.csrBase64()
 
    try:
      issuerPrivateKey = keys.RsaPrivateKey.Read(issuerPrivateKeyStr)
      ext = dps.x509Asn1Extractor.X509Asn1Extractor(issuerCert)
      
      #create x509
      transactionCount = model.Transaction().all().count()
      
      params = dps.x509CertificateParams.X509CertificateParams()
      subject = dps.x509AttributeAndValue.X509AttributeAndValueList()
      subjectItem = dps.x509AttributeAndValue.X509AttributeAndValue("Dealer tokens 1%08d" % transactionCount)
      subject.addItem(subjectItem)
      params.setSubject(subject)
      params.setIssuer(ext.getParams().getSubject())
      params.tokenType_ = "Dealer tokens"
      params.tokenQuantity_ = abs(value)
      params.redeemer_ = "http://someRedeemer.com"
      params.exchanger_ = "http://someExchanger.com"
      params.registrar_ = registrarAddress()
      dps.utils.out("params: "+str(params))
      
      msg = ""
      firstX509 = dps.x509Asn1Creator.X509Asn1Creator(params)
      firstX509.signWithPrivateKey(issuerPrivateKey)
      
      #fake csr      
      params = dps.x509CertificateParams.X509CertificateParams()
      csr = dps.csrAsn1Extractor.CsrAsn1Extractor(receiverCsrStr)      
      params.setSubject(csr.getSubject())
      params.setIssuer(subject)
      
      receiverPrivateKey = keys.RsaPrivateKey.Read(receiverPrivateKeyStr)
      secondX509 = dps.x509Asn1Creator.X509Asn1Creator(params, receiverPrivateKey)
      secondX509.signWithPrivateKey(firstX509.getRsaPrivateKey())
      
      trans = model.Transaction()
      trans.user_ = userentry
      trans.privateKey_ = str(firstX509.getRsaPrivateKey())
      trans.tokenValue_ = value
      trans.x509TokenCertBase64_ = firstX509.getBase64()
      trans.x509ReceiverCertBase64_ = secondX509.getBase64()
      trans.put()
      
      userentry.balance_ += value
      userentry.put()
      
      certChain = [secondX509.getBase64(), firstX509.getBase64(), issuerCert]
      
      prettyText = dps.x509Helper.prettyBase64Certificates(certChain)
      isOk, msg = uploadCerificateToRegistrar(prettyText)
      
    except google.appengine.runtime.DeadlineExceededError:
      msg = "Sorry. Operator timed out. Please try again."
  
    self.response.out.write(self.__bankPage(msg))
    
 # --------------------------------------------------------------------------------------------------------------------
class DetailHandler(DpsHandler):
  """ show certificate details """
  
  def get(self):
    transEntity = None
    prettyText = formattedBase64Text = tokenType = tokenQty = "Unable to find data"
    try:
      transId = self.request.get("id")
      transEntity = db.get(transId)
    except:
      pass
    
    if transEntity:
      basecert = dealer.Dealer.x509CertBase64()
      if transEntity.tokenValue_ > 0:
        #from the user
        basecert = transEntity.user_.userSelfSignedX509Base64_
        
      certChain = [transEntity.x509ReceiverCertBase64_, transEntity.x509TokenCertBase64_, basecert]
      prettyText = dps.x509Helper.prettyBase64Certificates(certChain, "<br/>")
          
    self.response.out.write("""%s<h2>Detail</h2>
                               <table border='1'>
                               <tr><td>Base 64 text:</td><td><tt>%s</tt></td></tr>
                               </table>%s""" % (standardHeader(), prettyText, standardFooter()))

# --------------------------------------------------------------------------------------------------------------------
def main():
  dps.utils.setLogMode(dps.utils.LogOutputModes.LOG_ONLY)

  application = webapp.WSGIApplication([("/roulette/play", PlayHandler),
                                       ("/roulette/bank", BankHandler),
                                       ("/roulette/detail", DetailHandler),
                                       ("/roulette/.*",  RouletteHandler),
                                       ("/intro/.*",  IntroHandler),
                                       ("/.*",  MainHandler)],
                                       debug=True)
  wutil.run_wsgi_app(application)

if __name__ == "__main__":
  main()
